#!/usr/bin/env python
#
# The main Gupicasa program
#
# Copyright (C) 2010 Yotam Medini <yotam.medini@gmail.com>
#
# This file is part of Gupicasa.
#
# Gupicasa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gupicasa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gupicasa.  If not, see <http://www.gnu.org/licenses/>.


# standard
import getpass
import inspect
import sys
import time
import urllib2

# local-gdata
import impgdata # local
gdata = impgdata.gdata
import atom
import gdata.photos
import gdata.photos.service
# local
import debug


class ActionFactory:

    def __init__(self):
        self.cmd2class = {}


    def add_class(self, klass, cmd):
        self.cmd2class[cmd] = klass


    def create_action(self, gupicasa, cmd, airef):
        action = None
        ai = airef[0]
        klass = self.cmd2class[cmd]
        args_spec = inspect.getargspec(klass.__init__)
        init_args = args_spec.args
        n_cmd_args = len(init_args) - 2 # skip (self, gupicasa)
        ai_next = ai + n_cmd_args
        if ai_next <= len(gupicasa.argv):
            klass_args = [gupicasa] + gupicasa.argv[ai: ai_next]
            action = klass(*klass_args)
            ai = ai_next
        airef[0] = ai
        return action


    def usage(self, indent, comment_column):
        t = ""
        sind = indent * ' '
        cmds = self.cmd2class.keys()[:]
        cmds.sort()
        for cmd in cmds:
            klass = self.cmd2class[cmd]
            args_spec = inspect.getargspec(klass.__init__)
            init_args = args_spec.args[2:] # skip (self, gupicasa)
            s = sind + '[-' + cmd
            for a in init_args:
                s += (" <%s>" % a)
            s += ']'
            lens = len(s)
            if lens < comment_column:
                s += (comment_column - lens) * ' '
            else:
                s += '\n' + comment_column * ' '
            s += '# ' + klass.__doc__ + '\n'
            t += s
        return t


action_factory = ActionFactory()


class Action:

    def __init__(self, gupicasa):
        self.gupicasa = gupicasa
        self.rc = 0

    def execute(self):
        pass


class QueryAlbums(Action):
    "Query Picasa albums"

    def __init__(self, gupicasa):
        debug.log("gupicasa=%s" % gupicasa)
        Action.__init__(self, gupicasa)
        debug.log("")

    def execute(self):
        debug.log("")
        self.gupicasa.qalbums()

action_factory.add_class(QueryAlbums, "qalbums")


class AlbumAdd(Action):
    "Create new album"

    def __init__(self, gupicasa, name, summary):
        debug.log("gupicasa=%s" % gupicasa)
        Action.__init__(self, gupicasa)
        self.name = name
        self.summary = summary

    def execute(self):
        debug.log("")
        self.gupicasa.album_add(self.name, self.summary)

action_factory.add_class(AlbumAdd, "addalbum")


class AlbumAction(Action):

    def __init__(self, gupicasa, album_id):
        Action.__init__(self, gupicasa)
        self.album_id = album_id


class AlbumDelete(AlbumAction):
    "Delete an empty album"

    def __init__(self, gupicasa, aid):
        AlbumAction.__init__(self, gupicasa, aid)

    def execute(self):
        debug.log("")
        self.gupicasa.album_del(self.album_id)

action_factory.add_class(AlbumDelete, "delalbum")


class AlbumRename(AlbumAction):
    "Rename album"

    def __init__(self, gupicasa, aid, name):
        AlbumAction.__init__(self, gupicasa, aid)
        self.name = name

    def execute(self):
        debug.log("")
        self.gupicasa.album_rename(self.album_id, self.name)

action_factory.add_class(AlbumRename, "albumrename")


class AlbumSummary(AlbumAction):
    "Set album's summary description"

    def __init__(self, gupicasa, aid, summary):
        AlbumAction.__init__(self, gupicasa, aid)
        self.summary = summary

    def execute(self):
        debug.log("")
        self.gupicasa.album_summary(self.album_id, self.summary)

action_factory.add_class(AlbumSummary, "albumsummary")


class QueryAlbum(AlbumAction):
    "Query album contents"

    def __init__(self, gupicasa, aid):
        AlbumAction.__init__(self, gupicasa, aid)

    def execute(self):
        debug.log("")
        self.gupicasa.qalbum(self.album_id)

action_factory.add_class(QueryAlbum, "qalbum")


class PhotoAction(AlbumAction):
    "Quary photo metadata"

    def __init__(self, gupicasa, album_id, photo_id):
        AlbumAction.__init__(self, gupicasa, album_id)
        self.photo_id = photo_id


class QueryPhoto(PhotoAction):
    "Quary photo metadata"

    def __init__(self, gupicasa, aid, pid):
        PhotoAction.__init__(self, gupicasa, aid, pid)

    def execute(self):
        debug.log("")
        self.gupicasa.qphoto(self.album_id, self.photo_id)

action_factory.add_class(QueryPhoto, "qphoto")


class GetPhoto(PhotoAction):
    "Download photo"

    def __init__(self, gupicasa, aid, pid, fn):
        PhotoAction.__init__(self, gupicasa, aid, pid)
        self.photo_fn = fn

    def execute(self):
        debug.log("")
        self.gupicasa.get_photo(self.album_id, self.photo_id, self.photo_fn)

action_factory.add_class(GetPhoto, "getphoto")


class PutPhoto(AlbumAction):
    "Upload photo"

    def __init__(self, gupicasa, aid, fn, caption):
        AlbumAction.__init__(self, gupicasa, aid)
        self.photo_fn = fn
        self.caption = caption

    def execute(self):
        debug.log("")
        self.gupicasa.put_photo(self.album_id, self.photo_fn, self.caption)

action_factory.add_class(PutPhoto, "putphoto")


class DeletePhoto(PhotoAction):
    "Delete photo"

    def __init__(self, gupicasa, aid, pid):
        PhotoAction.__init__(self, gupicasa, aid, pid)
        self.photo_id = pid

    def execute(self):
        debug.log("")
        self.gupicasa.del_photo(self.album_id, self.photo_id)

action_factory.add_class(DeletePhoto, "delphoto")


class Gupicasa:


    class AlbumInfo:

        def __init__(self, id, n, name, summary):
            self.id = id
            self.n = n
            self.name = name
            self.summary = summary


    class PhotoInfo:

        def __init__(self, photo_id, caption, width, height, size, epoch,
                     url, thumb_url):
            self.photo_id = photo_id
            self.caption = caption
            self.width = width
            self.height = height
            self.size = size
            self.epoch = epoch
            self.url = url
            self.thumb_url = thumb_url


        def get_image(self):
            return self.get_url_image(self.url)


        def get_thumb_image(self):
            return self.get_url_image(self.thumb_url)


        def get_url_image(self, url):
            debug.log("url=%s" % url)
            f = urllib2.urlopen(url)
            img = f.read()
            f.close()
            return img

        def epoch_to_ymd_hms(self):
            t = time.localtime(self.epoch)
            ymd_hms = "%d/%02d/%02d:%02d:%02d:%02d" % t[:6]
            return ymd_hms


        def get_caption_text(self):
            t = self.caption
            if t is None:
                t = ""
            return t.strip()


    def usage(self):
        utext = """
Usage:                           # (<aid>: Album Id,  <pid>: Photo Id)
  %s
    [-h | --help]                # This message
    [-gui | -nogui]              # Use (or not) graphical user interface.
    [-uname <username>]          # e.g: john.smith  (=> john.smith@gmail.com)
    [-pw <password>]             # Otherwise will be prompted for
"""[1:]

        us  = utext.split("\n")
        last_line = utext.split("\n")[-2]
        comment_column = last_line.find('#')
        utext += action_factory.usage(4, comment_column)

        utext += """
    [-gdata <dir>]               # Directory of Google's gdata Python module
"""[1:]
        sys.stderr.write(utext % self.argv[0])
        sys.stderr.write("""
By default, gui is active, unless some album action is given.
""")
        self.helped = True


    def __init__(self, argv):
        self.argv = argv
        self.rc = 0
        self.helped = False
        self.gui = None
        self.uname = None
        self.password = None
        self.service = None
        self.id2album = None
        self.actions = []
        ai = 1
        while ai < len(argv) and argv[ai][0] == '-' and self.mayrun():
            opt = argv[ai][1:]
            ai += 1
            val = argv[ai] if ai < len(argv) else None
            if opt in ('h', 'help', '-help'):
                self.usage()
            elif opt == 'gui':
                self.gui = True
            elif opt == 'nogui':
                self.gui = False
            elif opt == 'uname':
                ai += 1;  self.uname = val
            elif opt == 'pw':
                ai += 1;  self.password = val
            elif opt == 'gdata':
                ai += 1; # Already handled by impgdata
            elif opt in action_factory.cmd2class.keys():
                airef = [ai]
                action = action_factory.create_action(self, opt, airef)
                if action is None:
                    self.error("Failed creating action for -%s" % opt)
                self.actions.append(action)
                ai = airef[0]
            else:
                self.error("Failed option: '-%s'" % opt)
        if ai != len(argv):
            debug.log("ai=%d, len(argv)=%d" % (ai, len(argv)))
            self.error("Not handled tail arguments: %s" % str(argv[ai:]))
        if self.gui is None:
            self.gui = len(self.actions) == 0


    def ok(self):
        return self.rc == 0

    def mayrun(self):
        return self.ok() and not self.helped


    def error(self, msg):
        if self.ok():
            self.rc = 1
            sys.stderr.write("%s\n" % msg);
            self.usage()


    def log(self, msg):
        if self.gui:
            debug.log(msg)
        else:
            sys.stderr.write("%s\n" % msg)


    def run(self):
        debug.log("run: begin")
        if self.gui:
            import uimain
            ui = uimain.UI(self)
        else:
            if not None in (self.usage, self.password):
                self.login()
            self.do_actions()
            self.logout()
        debug.log("run: end")



    def assisted_login(self):
        debug.log("")
        if self.service is None:
            if self.uname is None:
                self.error("User-name not given")
            else:
                if self.password is None:
                    self.password = getpass.getpass()
                self.login()
        debug.log("done")


    def login(self):
        debug.log("")
        connected = True
        self.log("login")
        self.service = gdata.photos.service.PhotosService()
        self.service.email = "%s@gmail.com" % self.uname
        self.service.password = self.password
        # self.service.source = 'api-sample-google-com'
        try:
            self.service.ProgrammaticLogin()
        except Exception, e:
            self.log("login failed: %s" % e)
            self.logout() # & self.service = None
            connected = False;
        self.log("login done")
        return connected


    def logout(self):
        debug.log("")
        if self.service is not None:
            del self.service
            self.service = None


    def do_actions(self):
        debug.log("")
        if len(self.actions) > 0:
            self.assisted_login()
        if self.service is not None:
            for q in self.actions:
                if self.ok():
                    q.execute()


    def qalbums(self):
        self.log("")
        albums = self.get_albums_info()
        sys.stdout.write("%d albums\n" % len(albums))
        for a in albums:
            sys.stdout.write("[id=%s] (#=%3d) %s [%s]\n" %
                             (a.id, a.n, a.name, a.summary))


    def get_albums_info(self):
        debug.log("")
        self.id2album = {}
        albums = []
        feed = self.service.GetUserFeed(user=self.uname)
        for e in feed.entry:
            self.id2album[e.gphoto_id.text] = e
            n = int(e.numphotos.text)
            album = Gupicasa.AlbumInfo(e.gphoto_id.text, n, e.title.text,
                                      e.summary.text)
            albums.append(album)
        return albums


    def qalbum(self, album_id):
        self.log("qalbum album_id=%s" % album_id)
        photos = self.service.GetFeed(
            '/data/feed/api/user/%s/albumid/%s?kind=photo' %
            (self.uname, album_id))
        sys.stdout.write("%d photos\n" % len(photos.entry))
        for p in photos.entry:
            epoch = int(p.timestamp.text) / 1000
            ymd_hms = "%d/%02d/%02d:%02d:%02d:%02d" % time.localtime(epoch)[:6]
            sys.stdout.write("[id=%s] [%dx%d] [local=%s] %s\n" %
                             (p.gphoto_id.text,
                              p.width, p.height, ymd_hms,
                              p.summary.text))


    def get_album_photos_ids(self, album_id):
        debug.log("")
        feed = self.service.GetFeed(
            '/data/feed/api/user/%s/albumid/%s?kind=photo' %
            (self.uname, album_id))
        ids = []
        for e in feed.entry:
            ids.append(e.gphoto_id.text)
        return ids


    def get_photo_info(self, album_id, photo_id):
        debug.log("aid=%s pid=%s" % (album_id, photo_id))
        p = self.service.GetFeed(
            '/data/feed/api/user/%s/albumid/%s/photoid/%s' %
            (self.uname, album_id, photo_id))
        info = Gupicasa.PhotoInfo(
            photo_id=photo_id,
            caption=p.subtitle.text,
            width=int(p.width),
            height=int(p.height),
            size=p.size,
            epoch=(int(p.timestamp.text) / 1000),
            url=p.media.content[0].url,
            thumb_url=p.media.thumbnail[0].url)
        return info


    def get_photos_info(self, album_id):
        debug.log("aid=%s" % album_id)
        photos = self.service.GetFeed(
            '/data/feed/api/user/%s/albumid/%s?kind=photo' % (
                self.uname, album_id))
        infos = []
        for p in photos.entry:
            debug.log("photo_id=%s" % p.gphoto_id.text)
            info = Gupicasa.PhotoInfo(
                photo_id=p.gphoto_id.text,
                caption=p.summary.text,
                width=int(p.width),
                height=int(p.height),
                size=p.size,
                epoch=(int(p.timestamp.text) / 1000),
                url=p.media.content[0].url,
                thumb_url=p.media.thumbnail[0].url)
            infos.append(info)
        return infos


    def qphoto(self, album_id, photo_id):
        self.log("qphoto %s %s" % (album_id, photo_id))
        p = self.service.GetFeed(
            '/data/feed/api/user/%s/albumid/%s/photoid/%s' %
            (self.uname, album_id, photo_id))
        sys.stdout.write("title: %s\n" % p.title)
        sys.stdout.write("text: %s\n" % p.text)
        sys.stdout.write("size: %d\n" % p.size)
        sys.stdout.write("dimension: %dx%d\n" % (p.width, p.height))
        epoch = int(p.timestamp.text) / 1000
        ymd_hms = "%d/%02d/%02d:%02d:%02d:%02d" % time.localtime(epoch)[:6]
        sys.stdout.write("timestamp: %s\n" % ymd_hms)
        sys.stdout.write("Caption:: %s\n" % p.subtitle.text)

        self.log("Content url: %s" % p.media.content[0].url)
        sys.stdout.write("text: %s\n" % p.media.text)
        n = len( p.media.thumbnail)
        for i in range(n):
            tn =  p.media.thumbnail[i]
            sys.stdout.write("thumbnail[%d]: %dx%d  url=%s\n" %
                             (i, int(tn.width), int(tn.height), tn.url))



    def get_photo(self, album_id, photo_id, fn):
        self.log("get_photo aid=%s pid=%s fn=%s" % (album_id, photo_id, fn))
        p = self.service.GetFeed(
            '/data/feed/api/user/%s/albumid/%s/photoid/%s' %
            (self.uname, album_id, photo_id))
        content = p.media.content[0]
        self.log("url=%s" % content.url)
        f = urllib2.open(url)
        image_data = f.read()
        f.close()
        f = open(fn, "w")
        f.write(image_data)
        f.close()


    def del_photo(self, album_id, photo_id):
        self.log("del_photo aid=%s pid=%s" % (album_id, photo_id))
        p = self.service.GetFeed(
            '/data/feed/api/user/%s/albumid/%s/photoid/%s' %
            (self.uname, album_id, photo_id))
        self.service.Delete(p)


    def put_photo(self, album_id, fn, caption):
        self.log("put_photo: album_id=%s, fn=%s, caption=%s" %
                 (album_id, fn, caption))
        url = '/data/feed/api/user/%s/albumid/%s' % (self.uname, album_id)
        pe = self.service.InsertPhotoSimple(album_or_uri=url,
                                            title=fn,
                                            summary=caption,
                                            filename_or_handle=fn)
        # debug.log("pe=%s" % pe)
        debug.log("pe.rotation=%s" % pe.rotation)


    def album_add(self, name, summary):
        debug.log("name=%s, summary=%s" % (name, summary))
        self.service.InsertAlbum(title=name, summary=summary)


    def album_del(self, album_id):
        debug.log("album_id=%s" % album_id)
        album = self.get_album(album_id)
        ok = album is not None
        if ok:
            self.service.Delete(album)
        return ok


    def album_rename(self, album_id, name):
        debug.log("album_id=%s, name=%s" % (album_id, name))
        album = self.get_album(album_id)
        ok = album is not None
        if ok:
            album.title.text = name
            album = self.service.Put(album, album.GetEditLink().href,
                                     converter=gdata.photos.AlbumEntryFromString)
        return ok


    def album_summary(self, album_id, summary):
        debug.log("album_id=%s, summary=%s" % (album_id, summary))
        album = self.get_album(album_id)
        ok =  album is not None
        if ok:
            album.summary.text = summary
            album = self.service.Put(album, album.GetEditLink().href,
                                     converter=gdata.photos.AlbumEntryFromString)
        return ok


    def get_album(self, album_id):
        if self.id2album is None:
            self.get_albums_info()
        album = self.id2album.get(album_id, None)
        if album is None:
            self.error("Album %s not found" % album_id)
        return album


if __name__ == '__main__':
    p = Gupicasa(sys.argv)
    if p.mayrun():
        p.run()
    sys.exit(p.rc)
